CREATE SEQUENCE ACCOUNT_ID_SEQUENCE
START WITH 1
INCREMENT BY 1
NOMAXVALUE;

--事务的基本性质
/*原子性：事务中所有的SQL语句要么全部执行成功；要么全部不生效
一致性：事务成功提交后，事务中对所有数据的更改，将保证其前后是一致的
隔离性：当多个事务并发执行时，每个事务的执行对其它事务而言是不可见的
持久性：事务作出的数据更改将在事务成功提交后永久保存到数据库中*/
CREATE TABLE ACCOUNT(
  ACCOUNT_ID INTEGER PRIMARY KEY,
  ACCOUNT_NAME VARCHAR2(20),
  BALANCE NUMBER
);
--添加约束，账户余额不能小于0
ALTER TABLE ACCOUNT ADD CONSTRAINT ACCOUNT_BALANCE_CHK CHECK(BALANCE>=0);
--在oracle中，执行任何一条sql语句都表示一个事务的开始，因而事务的开始无需声明
--在事务中定义一个具有指定名称的保存点
savepoint savepoint_name;
--添加测试数据
INSERT INTO ACCOUNT VALUES(ACCOUNT_ID_SEQUENCE.NEXTVAL,'PILSEN',1000);
INSERT INTO ACCOUNT VALUES(ACCOUNT_ID_SEQUENCE.NEXTVAL,'JOHN',0);
--回滚指定保存点之后的操作，释放此保存点之后的数据表和行的锁
rollback to savepoint_name;

update ACCOUNT set BALANCE=BALANCE-1000 where ACCOUNT_NAME='JOHN';
update ACCOUNT set BALANCE=BALANCE+1000 where ACCOUNT_NAME='PILSEN';
--回滚当前事务，撤销当前事务对数据库所做的全部更改
rollback;
--声明事务的结束：提交事务，释放当前事务的锁，清除所有保存点
COMMIT;

--有些sql语句的事务提交是自动完成的，无需执行commit命令
--sqlplus命令：connect，disconnect，quit，exit
--数据定义语言：create|alter|drop table|view|sequence
--数据控制语言：grant，revoke

--事务的类型
--读写事务：事务的默认类型，可以对数据库执行读写操作，必须是事务的第一行语句
set transaction read write;

--对于一个事务读，另一个事务写的场景，前者两次从数据库中读到的数据可能出现不一致的问题
--对于这种问题，使用只读事务
--必须显式声明只读事务
set transaction read only;
--插入数据失败：不能在READ ONLY事务处理中执行插入/删除/更新操作
insert into account values(ACCOUNT_ID_SEQUENCE.NEXTVAL,'ROTH',1500);
--只读事务中所有的查询结果都是此事务开始前的数据（即使部分数据已经更新），直至使用commit声明事务结束
commit;

--在只读事务中可以执行以下语句
--select语句（select for update除外）
--lock table语句
--alter session语句
--alter system语句

/*数据并发和一致性*/
--两个事务同时对数据库进行写入操作时，可能造成第一个事务写入的数据被第二个事务写入的数据覆盖，导致第一次写入的部分数据丢失
--解决方案：对写入的每行数据加锁，即：对insert、update、delete语句影响的行加锁，这种锁称为“行锁”
--在事务结束后，这些锁被释放
--在第一个事务中执行更新语句，在执行commit命令提交事务前，受影响行的锁将一直存在
update account set account_name='NEW NAME 1' where rownum<=1;
--在第二个事务中执行更新语句时，当前事务被阻塞，原因是第一个事务锁定了预更新的行
update account set account_name='NEW NAME 2' where rownum<=1;

--手动加锁
--对查询结果中的所有行加锁
--如果一个查询语句指定了for update关键字，不能包含以下字句
--distinct
--group by
--集合运算符：intersect，minus，union
--聚合操作函数，如avg，count，min，max等

--在第一个事务中执行添加行锁的select语句
select * from account where balance<(select max(balance) from account) for update;
--查询余额最少的账户，其账户余额增加50
--在执行commit命令提交事务前，受影响行的锁将一直存在
update account set balance=balance+50 where balance<(select max(balance) from account);

--在第二个事务中执行更新语句时，当前事务被阻塞，原因是第一个事务锁定了预更新的行
update account set balance=balance+50 where balance<(select max(balance) from account);

--脏读：一个读事务和一个写事务同时进行时，读事务读取到的数据可能是写事务经过更新但还没有提交的数据
--这意味着读事务读取的数据不是最终数据，或者是被写事务丢弃的数据，这种情况即“脏读”

--对指定表施加排他（exclusive）锁
--除exclusive关键字外，还可以是share（共享）等
--nowait：如果指定的表，分区或表子分区已被其他用户锁定，是否希望数据库立即将控制权返回给用户
LOCK TABLE account IN exclusive MODE NOWAIT;

/*死锁：事务A对某些记录施加行锁后，随后试图操作B事务已经加锁的行，
相应地，B事务在释放自己的行锁前，须等待A释放占据的行锁，
最终导致两个事务相互等待，形成死锁
oracle会自动检测并解除死锁
当检测到死锁时，它会给引起死锁的其中一个事务发出一个错误信息，并回滚该事务的最后一条语句，该事务恢复工作
处于死锁状态的另一个事务也将脱离等待状态*/